﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Krile.Kernel.Control.Surface;
using Krile.Kernel.Data;
using Krile.Data;
using System.Threading;
using Krile.Kernel.WindowsEx;

namespace Krile.Forms.MainForm.Controls.Surface
{
    /// <summary>
    /// Surface core control
    /// </summary>
    internal partial class SurfaceCore : Control
    {
        SurfaceRefreshManager refreshman;
        SurfaceMouseManager mouseman;
        public SurfaceRefreshManager Refresher { get { return refreshman; } }

        SurfaceRenderer renderer;

        /// <summary>
        /// VerticalPositionが変更された
        /// </summary>
        internal event Action OnVerticalPositionChanged;

        /// <summary>
        /// 選択中のアイテムが変更された
        /// </summary>
        internal event Action OnSelectedItemChanged;

        /// <summary>
        /// ステータスの全体が変化した
        /// </summary>
        internal event Action OnStatusesChanged;

        /// <summary>
        /// Statusesのロックオブジェクト。
        /// <para>このオブジェクトを使ってのlockは、Statusesを列挙したい時以外に利用「しない」こと！</para>
        /// </summary>
        internal object statusesLocker = new object();
        private XStatus[] statuses = null;
        /// <summary>
        /// 表示するメッセージオブジェクト一覧
        /// </summary>
        internal XStatus[] Statuses
        {
            get { return statuses; }
            set
            {
                bool sus = Refresher.IsSuspending;
                if (!sus)
                    Refresher.Suspend();
                try
                {
                    if (Core.Config == null || Core.Config.Timeline.ListBottomNewer)
                    {
                        statuses = value;
                    }
                    else
                    {
                            ulong puid = 0;
                            long vp = 0;
                            if (statuses == null || value == null || Core.Config == null || SelectedIndex == -1 || SelectedStatus == null)
                                SelectedIndex = -1;
                            else
                            {
                                //選択ステータス描画開始位置の、画面上端からの位置を算出
                                puid = SelectedStatus.BaseStatus.UID;
                                vp = SelectedIndex * Core.Config.Appearance.Renderer.Detail.NormalListHeight;
                                vp -= VerticalPosition;
                            }
                            lock (statusesLocker)
                            {
                                statuses = value;
                            }
                            if (puid > 0)
                                SelectStatusByUID(puid, false);
                            if (Core.FormManager.MainForm == null || !Core.FormManager.MainForm.UnlockTimeline)
                            {
                                if (SelectedIndex != -1)
                                {
                                    //画面上端からの開始位置
                                    //絶対開始位置 - 画面上端の開始位置 = スクロールオフセット
                                    long ofs = SelectedIndex * Core.Config.Appearance.Renderer.Detail.NormalListHeight - vp;
                                    if (ofs > ListActualHeight - this.Height)
                                        ofs = ListActualHeight - this.Height;
                                    if (ofs < 0)
                                        ofs = 0;
                                    this.VerticalPosition = (int)ofs;
                                }
                            }
                    }
                    if (OnStatusesChanged != null)
                        OnStatusesChanged.Invoke();
                }
                finally
                {
                    if (!sus)
                        Refresher.Restore(true, true);
                    else
                        Refresher.RefreshSurface(true);
                }
            }
        }

        internal bool DrawTopDateTime
        {
            get { return renderer.drawTopDatetTime; }
            set
            {
                renderer.drawTopDatetTime = value;
                Refresh();
            }
        }

        
        internal void UpdateStatusWithSelectionPoint(IEnumerable<XStatus> status, ulong selected, long offset)
        {
            bool sus = Refresher.IsSuspending;
            if (!sus)
                Refresher.Suspend();
            try
            {
                lock (statusesLocker)
                {
                    statuses = status.ToArray();
                }
                if (selected > 0)
                    SelectStatusByUID(selected, false);
                if (SelectedIndex != -1)
                {
                    //絶対開始位置 - 画面上端の開始位置 = スクロールオフセット
                    long ofs = SelectedIndex * Core.Config.Appearance.Renderer.Detail.NormalListHeight - offset;
                    if (ofs > ListActualHeight - this.Height)
                        ofs = ListActualHeight - this.Height;
                    if (ofs < 0)
                        ofs = 0;
                    this.VerticalPosition = (int)ofs;
                }
                if (OnStatusesChanged != null)
                    OnStatusesChanged.Invoke();
            }
            finally
            {
                if (!sus)
                    Refresher.Restore(true, true);
                else
                    Refresher.RefreshSurface(true);
            }
        }

        private long _selectedIndex = -1;
        /// <summary>
        /// 選択されているメッセージオブジェクトのインデックス
        /// </summary>
        internal long SelectedIndex
        {
            get { return _selectedIndex; }
            set
            {
                bool sus = Refresher.IsSuspending;
                try
                {
                    if (!sus)
                        Refresher.Suspend();
                    //リソースの破棄
                    releaseSelectedItemResource();
                    _selectedIndex = value;
                    activateSelectedItemResource();
                    if (OnSelectedItemChanged != null)
                        OnSelectedItemChanged.Invoke();
                }
                finally
                {
                    if (!sus)
                        Refresher.Restore(true, true);
                    else
                        Refresher.RefreshSurface(true);
                }
            }
        }

        /// <summary>
        /// 選択中のステータス
        /// </summary>
        internal XStatus SelectedStatus
        {
            get
            {
                if (SelectedIndex >= 0 && SelectedIndex < statuses.Length)
                    return statuses[SelectedIndex];
                else
                    return null;
            }
        }

        /// <summary>
        /// リスト内部高さ
        /// </summary>
        internal int ListActualHeight
        {
            get
            {
                if (statuses != null && SelectedIndex >= 0)
                    return renderer.NormalItemHeight * (statuses.Length - 1) + renderer.SelectedItemHeight;
                else
                    if (statuses == null || statuses.Length == 0)
                        return 0;
                    else
                        return renderer.NormalItemHeight * statuses.Length;
            }
        }

        long verticalPosition = 0;
        /// <summary>
        /// Yスクロール位置を設定します。
        /// </summary>
        internal long VerticalPosition
        {
            get { return verticalPosition > ListActualHeight ? ListActualHeight : verticalPosition; }
            set
            {
                SetVerticalPosition(value, true);
            }
        }

        internal void SetVerticalPosition(long value, bool raiseVPChanged)
        {
            bool sus = Refresher.IsSuspending;
            try
            {
                if (!sus)
                    Refresher.Suspend();
                verticalPosition = value;
                if (raiseVPChanged && OnVerticalPositionChanged != null)
                    OnVerticalPositionChanged.Invoke();
            }
            finally
            {
                if (!sus)
                    Refresher.Restore(false, false);
                else
                    Refresher.RefreshSurface(false);
            }
        }

        /// <summary>
        /// 選択を解除
        /// </summary>
        /// <param name="update">描画を更新するか</param>
        internal void Deselect(bool update)
        {
            if (update)
                SelectedIndex = -1;
            else
                _selectedIndex = -1;
        }

        //********************************* [ Supernatural Border ! ] *********************************

        /// <summary>
        /// コンストラクタ
        /// </summary>
        internal SurfaceCore()
        {
            refreshman = new SurfaceRefreshManager(InvokeRefresh);
            renderer = new SurfaceRenderer(this);
            mouseman = new SurfaceMouseManager(this, renderer);

            InitializeComponent();
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.Opaque, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            SetStyle(ControlStyles.Selectable, true);
            SetStyle(ControlStyles.UserMouse, true);
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.ResizeRedraw, true);
            SetStyle(ControlStyles.FixedHeight, true);
            SetStyle(ControlStyles.FixedWidth, true);

            //Reservation for surface update thread stopping
            Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
            Core.OnExit += new Action(Core_OnExit);
            updateSignal = new AutoResetEvent(false);
            updateThread = new Thread(new ThreadStart(UpdateLoop));
            updateThread.Name = "DisplayUpdateLoop";
            updateThread.Start();
        }

        void Core_OnExit()
        {
            updateThread.Abort();
            UpdateLoopEnabled = false;
            updateSignal.Set();
        }

        void Application_ApplicationExit(object sender, EventArgs e)
        {
            UpdateLoopEnabled = false;
            updateSignal.Set();
        }

        private volatile bool UpdateLoopEnabled = true;
        volatile bool recompositeRequired = false;
        AutoResetEvent updateSignal = null;
        Thread updateThread = null;
        private void InvokeRefresh(bool recompz)
        {
            if (recompz)
                recompositeRequired = true;
            updateSignal.Set();
        }

        /// <summary>
        /// Surface refresh thread
        /// </summary>
        private void UpdateLoop()
        {
            while (UpdateLoopEnabled)
            {
                updateSignal.WaitOne(5000);
                if (!UpdateLoopEnabled)
                    break;
                if (recompositeRequired)
                {
                    recompositeRequired = false;
                    try
                    {
                        this.SafeOperate(_doRefreshUnsafeComposite);
                    }
                    catch (System.ComponentModel.InvalidAsynchronousStateException) { } // なんか原因不明
                }
                else
                {
                    _doRefreshSafe();
                }
                Thread.Sleep(0);
            }
        }

        private void _doRefreshSafe()
        {
            this.SafeOperate(base.Refresh);
        }

        private void _doRefreshUnsafeComposite()
        {
            renderer.UpdateComposite();
            base.Refresh();
        }

        public override void Refresh()
        {
            Refresher.RefreshSurface(false);
        }

        internal void Refresh(bool recomposite)
        {
            Refresher.RefreshSurface(recomposite);
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            if (Core.Config != null)
                pe.Graphics.InterpolationMode = Core.Config.Appearance.InterpolateMode;
            renderer.Draw(pe.Graphics, (int)verticalPosition);
        }

        protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Up:
                case Keys.Down:
                case Keys.Left:
                case Keys.Right:
                    e.IsInputKey = true;
                    return;
            }
            base.OnPreviewKeyDown(e);
        }

        private bool IsListHasItem
        {
            get
            {
                return statuses != null && statuses.Length != 0;
            }
        }

        private void activateSelectedItemResource()
        {
            if (SelectedStatus != null && SelectedStatus.BaseStatus.Text.MessageObjects != null)
                foreach (var msgobj in SelectedStatus.BaseStatus.Text.MessageObjects)
                    msgobj.Activate();
        }

        private void releaseSelectedItemResource()
        {
            if (SelectedStatus != null && SelectedStatus.BaseStatus.Text.MessageObjects != null)
                foreach (var msgobj in SelectedStatus.BaseStatus.Text.MessageObjects)
                    msgobj.Release();
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            Refresher.RefreshSurfaceDelay(true);
            base.OnSizeChanged(e);
        }

        public void EnsureVisible()
        {
            if (SelectedIndex < 0) return;
            long vs = SelectedIndex * Core.Config.Appearance.Renderer.Detail.NormalListHeight;
            long evs = vs + renderer.SelectedItemHeight;
            long cp = this.verticalPosition;
            long mp = this.ListActualHeight - this.Height;
            //表示末尾位置よりスクロール位置+画面高さの方が小さい == (一部)見えてない
            if (evs > cp + this.Height)
                cp = evs - this.Height;
            //表示開始位置よりスクロール位置の方が大きい == 見えてない
            if (vs < cp)
                cp = vs;
            if (cp > mp)
                cp = mp;
            if (cp < 0)
                cp = 0;
            this.VerticalPosition = cp;
        }

        public bool SelectStatusByUID(ulong uid, bool evisible)
        {
            int target = -1;
            lock (statusesLocker)
            {
                for (int idx = 0; idx < statuses.Length; idx++)
                {
                    if (statuses[idx].BaseStatus.UID == uid)
                    {
                        target = idx;
                        break;
                    }
                }
            }
            if(target ==  -1)
                return false;

            var sus = Refresher.IsSuspending;
            if (!sus)
                Refresher.Suspend();
            try
            {
                this.SelectedIndex = target;
                if (evisible)
                    EnsureVisible();
            }
            finally
            {
                if (!sus)
                    Refresher.Restore(true, false);
                else
                    Refresher.RefreshSurface(true);
            }
            return true;
        }

        /// <summary>
        /// Generate context menu strip
        /// </summary>
        /// <returns></returns>
        internal ContextMenuStrip CreateContextMenu()
        {
            if (SelectedStatus == null)
                return null;
            ContextMenuStrip ret = new ContextMenuStrip();
            if (SelectedStatus.BaseStatus.ParentAgent.Status.AutoCreateReplyOperationTriggers)
                ret.Items.Add(new ToolStripMenuItem(Lang.Renderer.Context_Reply, Properties.Resources.comment_add, new EventHandler(Ctx_SetReply)));
            if (SelectedStatus.BaseStatus.ParentAgent.Status.AutoCreateFavOperationTriggers)
            {
                var fav = new ToolStripMenuItem(Lang.Renderer.Context_SetFav, Properties.Resources.star, new EventHandler(Ctx_SetFav));
                if (SelectedStatus.BaseStatus.Favored)
                    fav.Checked = true;
                ret.Items.Add(fav);
            }
            if (Core.FormManager.MainForm.CustomContextMenus != null)
            {
                foreach (var i in Core.FormManager.MainForm.CustomContextMenus)
                {
                    if (i.IsShowMenu(SelectedStatus.BaseStatus))
                        ret.Items.Add(i.GetMenuItem());
                }
            }
            ret.Items.Add(new ToolStripSeparator());
            var extTS = new ToolStripMenuItem(Lang.Renderer.Context_ExtractOrJump);
            extTS.DropDownItems.Add(new ToolStripMenuItem(Lang.Renderer.Context_E_ExtractUser, Properties.Resources.searchuser, new EventHandler(Ctx_ExtractUser)));
            extTS.DropDownItems.Add(new ToolStripMenuItem(Lang.Renderer.Context_E_ExtractService, Properties.Resources.search, new EventHandler(Ctx_ExtractService)));
            if (SelectedStatus.BaseStatus.ReplyData != null)
            {
                extTS.DropDownItems.Add(new ToolStripSeparator());
                extTS.DropDownItems.Add(new ToolStripMenuItem(Lang.Renderer.Context_E_JumpToReply, Properties.Resources.comment, new EventHandler(Ctx_JumpToReply)));
                extTS.DropDownItems.Add(new ToolStripMenuItem(Lang.Renderer.Context_E_ExtractReplies, Properties.Resources.comments, new EventHandler(Ctx_ExtractReplies)));
                extTS.DropDownItems.Add(new ToolStripMenuItem(Lang.Renderer.Context_E_Conversation, Properties.Resources.user_comment, new EventHandler(Ctx_ExtractConversation)));
            }
            ret.Items.Add(extTS);
            var copyTS = new ToolStripMenuItem(Lang.Renderer.Context_Copy);
            copyTS.DropDownItems.Add(new ToolStripMenuItem(Lang.Renderer.Context_C_Text, Properties.Resources.copy, new EventHandler(Ctx_CopyText)));
            copyTS.DropDownItems.Add(new ToolStripMenuItem(Lang.Renderer.Context_C_UserID, Properties.Resources.user, new EventHandler(Ctx_CopyUserID)));
            copyTS.DropDownItems.Add(new ToolStripMenuItem(Lang.Renderer.Context_C_Permalink, Properties.Resources.permalink, new EventHandler(Ctx_CopyPermalink)));
            ret.Items.Add(copyTS);
            var obTS = new ToolStripMenuItem(Lang.Renderer.Context_OpenBrowser);
            obTS.DropDownItems.Add(new ToolStripMenuItem(Lang.Renderer.Context_O_Status, Properties.Resources.comment, new EventHandler(Ctx_OpenWebStatus)));
            obTS.DropDownItems.Add(new ToolStripMenuItem(Lang.Renderer.Context_O_User, Properties.Resources.searchuser, new EventHandler(Ctx_OpenWebUser)));
            ret.Items.Add(obTS);
            ret.Items.Add(new ToolStripMenuItem(Lang.Renderer.Context_Delete, Properties.Resources.delete, new EventHandler(Ctx_DeleteStatus)));
            ret.Items.Add(new ToolStripSeparator());
            ret.Items.Add(new ToolStripMenuItem(Lang.Renderer.Context_Deselect, null, new EventHandler(Ctx_Deselect)));
            return ret;
        }

        internal ContextMenuStrip CreateReplyContextMenu()
        {
            if (SelectedStatus == null || SelectedStatus.BaseStatus.ReplyData == null)
                return null;
            var rep = new ContextMenuStrip();
            rep.Items.Add(new ToolStripMenuItem(Lang.Renderer.Context_E_JumpToReply, Properties.Resources.comment, new EventHandler(Ctx_JumpToReply)));
            rep.Items.Add(new ToolStripMenuItem(Lang.Renderer.Context_E_ExtractReplies, Properties.Resources.comments, new EventHandler(Ctx_ExtractReplies)));
            rep.Items.Add(new ToolStripSeparator());
            rep.Items.Add(new ToolStripMenuItem(Lang.Renderer.Context_E_Conversation, Properties.Resources.user_comment, new EventHandler(Ctx_ExtractConversation)));
            return rep;
        }

        private void Ctx_SetReply(object o, EventArgs e)
        {
            if (SelectedStatus != null)
                Core.FormManager.MainForm.InputControlManager.SetReplyTo(SelectedStatus.BaseStatus.UID);
        }

        private void Ctx_SetFav(object o, EventArgs e)
        {
            if (SelectedStatus != null)
            {
                Action act = new Action(() =>
                {
                    var pf = this.GetParentForm();
                    Status target = SelectedStatus.BaseStatus;
                    if (pf != null)
                        pf.RefreshTL(false);
                    Core.FormManager.MainForm.SetStatusText(Lang.StatusText.Favouring);
                    var or = target.ParentAgent.Status.SetFavorite(target.UID, !target.Favored);
                    if (or.Succeeded)
                    {
                        target.Favored = !target.Favored;
                    }
                    else
                    {
                        Subsystem.Debugger.AddReport(new Krile.Kernel.Bridges.DebugReport("Fav operation was failed at " + target.UID.ToString()));
                        Core.FormManager.MainForm.SetStatusText(Lang.StatusText.FavError, 3);
                    }
                    if (pf != null)
                        pf.RefreshTL(true);
                    Core.FormManager.MainForm.SetStatusText(Lang.StatusText.Favored, 3);
                    Core.StatusManager.UpdateStatus(new[] { target.UID });
                });
                act.BeginInvoke((iar) => ((Action)iar.AsyncState).EndInvoke(iar), act);
            }
        }

        private void Ctx_CopyText(object o, EventArgs e)
        {
            if (SelectedStatus != null)
            {
                Clipboard.SetText(SelectedStatus.BaseStatus.Text.Original);
                Core.FormManager.MainForm.SetStatusText(Lang.StatusText.Copied, 3);
            }
        }

        private void Ctx_CopyUserID(object o, EventArgs e)
        {
            if (SelectedStatus != null)
            {
                Clipboard.SetText(SelectedStatus.BaseStatus.User.Id);
                Core.FormManager.MainForm.SetStatusText(Lang.StatusText.Copied, 3);
            }
        }

        private void Ctx_CopyPermalink(object o, EventArgs e)
        {
            if (SelectedStatus != null)
            {
                Clipboard.SetText(SelectedStatus.BaseStatus.StatusPage.OriginalString);
                Core.FormManager.MainForm.SetStatusText(Lang.StatusText.Copied, 3);
            }
        }

        private void Ctx_JumpToReply(object o, EventArgs e)
        {
            if (SelectedStatus != null)
                Data.DefaultKeyAssigns.JumpInReplyTo(this.GetParentForm(), SelectedStatus.BaseStatus, Keys.None);
        }

        private void Ctx_ExtractUser(object o, EventArgs e)
        {
            if (SelectedStatus != null)
                Data.DefaultKeyAssigns.ExtractThisUser(this.GetParentForm(), SelectedStatus.BaseStatus, Keys.None);
        }

        private void Ctx_ExtractService(object o, EventArgs e)
        {
            if (SelectedStatus != null)
                Data.DefaultKeyAssigns.ExtractThisService(this.GetParentForm(), SelectedStatus.BaseStatus, Keys.None);
        }

        private void Ctx_ExtractReplies(object o, EventArgs e)
        {
            if (SelectedStatus != null)
                Data.DefaultKeyAssigns.ExtractThisPostsReply(this.GetParentForm(), SelectedStatus.BaseStatus, Keys.None);
        }

        private void Ctx_ExtractConversation(object o, EventArgs e)
        {
            if (SelectedStatus != null)
                Data.DefaultKeyAssigns.ExtractConversation(this.GetParentForm(), SelectedStatus.BaseStatus, Keys.None);
        }

        private void Ctx_OpenWebStatus(object o, EventArgs e)
        {
            if (SelectedStatus != null)
            {
                Data.DefaultKeyAssigns.ShowStatusPage(this.GetParentForm(), SelectedStatus.BaseStatus, Keys.None);
            }
        }

        private void Ctx_OpenWebUser(object o, EventArgs e)
        {
            if (SelectedStatus != null)
                Subsystem.CommonOperation.CallBrowser(SelectedStatus.BaseStatus.User.ProfileUri.OriginalString);
        }

        private void Ctx_DeleteStatus(object o, EventArgs e)
        {
            if (SelectedStatus != null)
                Core.StatusManager.RemoveStatusWithOnSvr(SelectedStatus.BaseStatus);
        }

        private void Ctx_Deselect(object o, EventArgs e)
        {
            if (Core.FormManager.MainForm.PreviousForegroundForm != null)
                Core.FormManager.MainForm.PreviousForegroundForm.DeselectStatus();
        }
    }
}
